package io.cici.cc.mybatis.lite.builder;

import io.cici.cc.mybatis.lite.mapping.ParameterMapping;
import io.cici.cc.mybatis.lite.parse.TokenHandler;
import io.cici.cc.mybatis.lite.reflection.ClassMetaData;
import io.cici.cc.mybatis.lite.reflection.MetaDataObject;
import io.cici.cc.mybatis.lite.session.Configuration;
import io.cici.cc.mybatis.lite.type.TypeHandler;
import io.cici.cc.mybatis.lite.type.TypeHandlerRegistry;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * @author huihui
 */
public class ParameterMappingTokenHandler extends Builder implements TokenHandler {

    private final List<ParameterMapping> parameterMappingList = new ArrayList<>();
    private final Class<?> parameterType;
    private final MetaDataObject metaDataObject;

    public ParameterMappingTokenHandler(Configuration configuration, Class<?> parameterType, Map<String, Object> additionalParameters) {
        super(configuration);
        this.parameterType = parameterType;
        this.metaDataObject = configuration.newMetaDataObject(additionalParameters);
    }

    public List<ParameterMapping> getParameterMappingList() {
        return parameterMappingList;
    }

    @Override
    public String handle(String content) {
        parameterMappingList.add(buildParameterMapping(content));
        return "?";
    }

    private ParameterMapping buildParameterMapping(String content) {
        Map<String, String> propertiesMap = parseParameterMapping(content);
        String property = propertiesMap.get("property");
        String type = propertiesMap.get("type");
        TypeHandler typeHandler = TypeHandlerRegistry.getTypeHandler(type);
        Class<?> propertyType;
        if (metaDataObject.hasGetter(property)) {
            propertyType = metaDataObject.getGetterType(property);
        } else if (property == null || Map.class.isAssignableFrom(parameterType)) {
            propertyType = Object.class;
        } else {
            ClassMetaData classMetaData = ClassMetaData.build(Configuration.reflectorFactory, parameterType);
            if (classMetaData.hasGetter(property)) {
                propertyType = classMetaData.getGetterType(property);
            } else {
                propertyType = Object.class;
            }
        }
        ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, null, type, typeHandler);

        return builder.build();
    }

    private Map<String, String> parseParameterMapping(String content) {
        try {
            return new ParameterExpression(content);
        } catch (BuilderException ex) {
            throw ex;
        } catch (Exception ex) {
            throw new BuilderException("Parsing error was found in mapping #{" + content
                    + "}.  Check syntax #{property|(expression), var1=value1, var2=value2, ...} ", ex);
        }
    }
}
